/**
 * 感谢 https://github.com/chaangliu/ForceDirectedLayout/blob/master/javascript/force-directed.js
 * A force directed graph layout implementation by liuchang on 2018/05/10.
 */
const CANVAS_WIDTH = 1000;
const CANVAS_HEIGHT = 1000;
let k;
let mNodeList = [];
let mEdgeList = [];
let mDxMap = {};
let mDyMap = {};
let mNodeMap = {};

export function ForceDirected(data = {}) {
  // generate nodes and edges
  // for (let i = 0; i < 20; i++) {
  //     mNodeList.push(new Node(i))
  // }
  k = 0;
  mNodeList = [];
  mEdgeList = [];
  mDxMap = {};
  mDyMap = {};
  mNodeMap = {};

  let nodeList = data.nodeList;
  for (let i = 0; i < nodeList.length; i++) {
    let node = nodeList[i];
    mNodeList.push(node);
  }

  // for (let i = 0; i < 20; i++) {
  //     let edgeCount = Math.random() * 8 + 1
  //     for (let j = 0; j < edgeCount; j++) {
  //         let targetId = Math.floor(Math.random() * 20)
  //         let edge = new Edge(i, targetId)
  //         mEdgeList.push(edge)
  //     }
  // }
  // line 转 edge
  let lineList = data.lineList;
  for (let i = 0; i < lineList.length; i++) {
    let line = lineList[i];
    let edge = new Edge(line.from, line.to);
    mEdgeList.push(edge);
  }

  if (mNodeList && mEdgeList) {
    k = Math.sqrt((CANVAS_WIDTH * CANVAS_HEIGHT) / mNodeList.length);
  }
  for (let i = 0; i < mNodeList.length; i++) {
    let node = mNodeList[i];
    if (node) {
      mNodeMap[node.id] = node;
    }
  }

  // 随机生成坐标. Generate coordinates randomly.
  let initialX,
    initialY,
    initialSize = 40.0;
  for (let i in mNodeList) {
    initialX = CANVAS_WIDTH * 0.5;
    initialY = CANVAS_HEIGHT * 0.5;
    mNodeList[i].x = initialX + initialSize * (Math.random() - 0.5);
    mNodeList[i].y = initialY + initialSize * (Math.random() - 0.5);
  }

  // 迭代200次. Iterate 200 times.
  for (let i = 0; i < 200; i++) {
    calculateRepulsive();
    calculateTraction();
    updateCoordinates();
  }
  // console.log(JSON.stringify(new Result(mNodeList, mEdgeList)))
  // 坐标添加px
  for (let i = 0; i < mNodeList.length; i++) {
    let node = mNodeList[i];
    node.left = node.x + "px";
    node.top = node.y + "px";
    node.x = undefined;
    node.y = undefined;
  }

  data.nodeList = mNodeList;

  // console.log(data)
  return data;
}

function Node(id = null) {
  this.id = id;
  this.x = 22;
  this.y = null;
}

function Edge(source = null, target = null) {
  this.source = source;
  this.target = target;
}

/**
 * 计算两个Node的斥力产生的单位位移。
 * Calculate the displacement generated by the repulsive force between two nodes.*
 */
function calculateRepulsive() {
  let ejectFactor = 6;
  let distX, distY, dist;
  for (let i = 0; i < mNodeList.length; i++) {
    mDxMap[mNodeList[i].id] = 0.0;
    mDyMap[mNodeList[i].id] = 0.0;
    for (let j = 0; j < mNodeList.length; j++) {
      if (i !== j) {
        distX = mNodeList[i].x - mNodeList[j].x;
        distY = mNodeList[i].y - mNodeList[j].y;
        dist = Math.sqrt(distX * distX + distY * distY);
      }
      if (dist < 30) {
        ejectFactor = 5;
      }
      if (dist > 0 && dist < 250) {
        let id = mNodeList[i].id;
        mDxMap[id] = mDxMap[id] + (((distX / dist) * k * k) / dist) * ejectFactor;
        mDyMap[id] = mDyMap[id] + (((distY / dist) * k * k) / dist) * ejectFactor;
      }
    }
  }
}

/**
 * 计算Edge的引力对两端Node产生的引力。
 * Calculate the traction force generated by the edge acted on the two nodes of its two ends.
 */
function calculateTraction() {
  let condenseFactor = 3;
  let startNode, endNode;
  for (let e = 0; e < mEdgeList.length; e++) {
    let eStartID = mEdgeList[e].source;
    let eEndID = mEdgeList[e].target;
    startNode = mNodeMap[eStartID];
    endNode = mNodeMap[eEndID];
    if (!startNode) {
      console.log("Cannot find start node id: " + eStartID + ", please check it out.");
      return;
    }
    if (!endNode) {
      console.log("Cannot find end node id: " + eEndID + ", please check it out.");
      return;
    }
    let distX, distY, dist;
    distX = startNode.x - endNode.x;
    distY = startNode.y - endNode.y;
    dist = Math.sqrt(distX * distX + distY * distY);
    mDxMap[eStartID] = mDxMap[eStartID] - ((distX * dist) / k) * condenseFactor;
    mDyMap[eStartID] = mDyMap[eStartID] - ((distY * dist) / k) * condenseFactor;
    mDxMap[eEndID] = mDxMap[eEndID] + ((distX * dist) / k) * condenseFactor;
    mDyMap[eEndID] = mDyMap[eEndID] + ((distY * dist) / k) * condenseFactor;
  }
}

/**
 * 更新坐标。
 * update the coordinates.
 */
function updateCoordinates() {
  let maxt = 4,
    maxty = 3; // Additional coefficients.
  for (let v = 0; v < mNodeList.length; v++) {
    let node = mNodeList[v];
    let dx = Math.floor(mDxMap[node.id]);
    let dy = Math.floor(mDyMap[node.id]);

    if (dx < -maxt) dx = -maxt;
    if (dx > maxt) dx = maxt;
    if (dy < -maxty) dy = -maxty;
    if (dy > maxty) dy = maxty;
    node.x = node.x + dx >= CANVAS_WIDTH || node.x + dx <= 0 ? node.x - dx : node.x + dx;
    node.y = node.y + dy >= CANVAS_HEIGHT || node.y + dy <= 0 ? node.y - dy : node.y + dy;
  }
}

function Result(nodes = null, links = null) {
  this.nodes = nodes;
  this.links = links;
}
